Skip to content

standalone doenetml in iframes can initialize in sequence#880

Open
dqnykamp wants to merge 9 commits intoDoenet:mainfrom
dqnykamp:serialize-standalone-initiation
Open

standalone doenetml in iframes can initialize in sequence#880
dqnykamp wants to merge 9 commits intoDoenet:mainfrom
dqnykamp:serialize-standalone-initiation

Conversation

@dqnykamp
Copy link
Copy Markdown
Member

@dqnykamp dqnykamp commented Feb 23, 2026

This PR adds two coordination modes to standalone DoenetML documents embedded in iframes: dom-order and viewport-only.

Without coordination (the default), all DoenetML activities initialize at once. This behavior can lead to performance and memory issues in a page that contains many DoenetML activities.

To force the DoenetML activities to initialize in sequence, we've added an enableParentCoordination flag that can be added when embedding a DoenetML activity. If this flag as specified, the DoenetML will wait for a signal from its iframe parent before initializing.

The coordination is enabled and the activity intialization is unlocked by calling a new function initializeDoenetParentCoordinator() in the parent of the iframes. It will coordinate the initialization to occur either in the order that elements appear in the DOM (default "dom-order" strategy) or to prioritize activities that are currently visible (the "viewport-first" strategy).

The PR also updates documentation about the coordination and updates the README.

Resolves #874.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a coordination mechanism for standalone DoenetML documents embedded in iframes to address memory and performance issues when multiple activities initialize simultaneously. The solution adds parent-child iframe coordination with two strategies: dom-order (sequential initialization) and viewport-first (prioritize visible iframes).

Changes:

  • Added initializeDoenetParentCoordinator() function to manage serialized iframe initialization with configurable strategies
  • Enhanced renderDoenetViewerToContainer() to support enableParentCoordination flag and message-based coordination with parent window
  • Implemented comprehensive test suite with Cypress tests covering uncoordinated and coordinated scenarios
  • Added extensive documentation in README.md and new COORDINATION.md explaining usage patterns and implementation details

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 22 comments.

Show a summary per file
File Description
packages/standalone/src/index.tsx Core implementation of parent-child coordination using postMessage protocol, IntersectionObserver for visibility tracking, and strategy-based iframe initialization
packages/standalone/README.md Updated with quick start examples, API reference, and coordination configuration
packages/standalone/COORDINATION.md New comprehensive documentation covering coordination strategies, timing, edge cases, and implementation details
packages/standalone/iframe-parent.html Demo page showing dom-order coordination strategy
packages/standalone/iframe-parent-viewport-first.html Demo page showing viewport-first coordination strategy
packages/standalone/iframe-child.html Child iframe demo with parent coordination enabled
packages/standalone/index.html Cleaned up test attributes from viewer containers
packages/test-cypress/vite.config.ts Added standalone dist files to static copy for Cypress tests
packages/test-cypress/package.json Added standalone package as build dependency and public files to watch list
packages/test-cypress/public/standaloneBlobUrls.js Utility module for loading standalone scripts/CSS as blob URLs in tests
packages/test-cypress/public/uncoordinated-same-page.html Test page with multiple viewers on same page without coordination
packages/test-cypress/public/uncoordinated-iframes.html Test page with multiple uncoordinated iframes
packages/test-cypress/public/uncoordinated-child.html Child page for uncoordinated iframe tests
packages/test-cypress/public/coordination-dom-order.html Test page demonstrating dom-order coordination strategy
packages/test-cypress/public/coordination-viewport-first.html Test page demonstrating viewport-first coordination with scroll scenarios
packages/test-cypress/public/coordination-child.html Child page with coordination enabled for tests
packages/test-cypress/cypress/support/commands.js Added getIframeBody helper command for testing iframe content
packages/test-cypress/cypress/e2e/standalone/uncoordinated-same-page.cy.js Tests for multiple viewers on same page
packages/test-cypress/cypress/e2e/standalone/uncoordinated-iframes.cy.js Tests for uncoordinated iframe initialization
packages/test-cypress/cypress/e2e/standalone/coordination-dom-order.cy.js Tests verifying strict DOM order initialization
packages/test-cypress/cypress/e2e/standalone/coordination-viewport-first.cy.js Tests verifying viewport-first prioritization from top and bottom
packages/doenetml/src/index.ts Exported DoenetMLFlags type for better TypeScript support

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/standalone/src/index.tsx
Comment thread packages/standalone/src/index.tsx Outdated
Comment thread packages/standalone/src/index.tsx Outdated
Comment thread packages/standalone/src/index.tsx Outdated
Comment thread packages/standalone/src/index.tsx Outdated
Comment thread packages/standalone/src/index.tsx
Comment thread packages/test-cypress/vite.config.ts Outdated
Comment thread packages/standalone/COORDINATION.md Outdated
Comment thread packages/standalone/src/index.tsx
Comment thread packages/standalone/src/index.tsx
@dqnykamp dqnykamp requested review from siefkenj February 24, 2026 04:28

const viewerRoots = new WeakMap<Element, ReactDOM.Root>();

type ViewerConfig = Record<string, any>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be happier if this were an actual type with appropriate keys.

...flags
} = attrs;

const localConfig = { ...(config ?? {}) };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a default argument instead of ?? {}

const userOnInit = localConfig.onInit;
delete localConfig.flags;
delete localConfig.initializedCallback;
delete localConfig.onInit;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleteing things from an object smells like an anti-pattern

const visibilityRootMargin =
localConfig.visibilityRootMargin ??
visibilityRootMarginFromAttrs ??
"600px";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How much configuration does this all need? Could this be served by a top-level const instead? Is it going to vary from situation to situation?

const shouldCoordinate =
localConfig.enableParentCoordination ??
enableParentCoordination ??
false;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want the default to be false? I'm pretty unhappy with the way this code is layed out. It makes defaults very hard to find without reading the whole file.


// Check if we can access parent (same origin)
try {
void window.parent.location.origin;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why void? Won't this test work without this?

}

// Set up coordination with parent
const iframeId = `iframe_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make a uniqueId function if you're going to use one multiple times (I believe you already have one somewhere in the code for all your sending back and forth of scores and whatnot)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Memory use is very high on PreteXt book pages with multiple embedded Doenet activities

3 participants